/*
 *
 * (C) 2014 David Lettier.
 *
 * http://www.lettier.com/
 *
 * NTP client.
 *
 * Compiled with gcc version 4.7.2 20121109 (Red Hat 4.7.2-8) (GCC).
 *
 * Tested on Linux 3.8.11-200.fc18.x86_64 #1 SMP Wed May 1 19:44:27 UTC 2013 x86_64 x86_64 x86_64 GNU/Linux.
 *
 * To compile: $ gcc main.c -o ntpClient.out
 *
 * Usage: $ ./ntpClient.out
 *
 */

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/socket.h>
#ifndef MELIS_OS
#include <netinet/in.h>
#endif
#include <netdb.h>
#include <fcntl.h>

#define LOG_TAG "ntpclient"
#include "osal_log.h"

#define NTP_TIMESTAMP_DELTA 2208988800ull

#define LI(packet)   (uint8_t) ((packet.li_vn_mode & 0xC0) >> 6) // (li   & 11 000 000) >> 6
#define VN(packet)   (uint8_t) ((packet.li_vn_mode & 0x38) >> 3) // (vn   & 00 111 000) >> 3
#define MODE(packet) (uint8_t) ((packet.li_vn_mode & 0x07) >> 0) // (mode & 00 000 111) >> 0

int ntpclient( int argc, char* argv[ ] )
{
  int i = 0;
  int sockfd, n; // Socket file descriptor and the n return result from writing/reading from the socket.

  int portno = 123; // NTP UDP port number.

  char * ntp_host_list[128] ={
    "ntp.aliyun.com",
    "cn.ntp.org.cn",
    "cn.pool.ntp.org",
    "asia.pool.ntp.org",
    "time.windows.com",
    "time.asia.apple.com"
  };
  int ntp_host_cnt = 6;

  char* host_name = ntp_host_list[0]; // NTP server host-name.

  // Structure that defines the 48 byte NTP packet protocol.

  typedef struct
  {

    uint8_t li_vn_mode;      // Eight bits. li, vn, and mode.
                             // li.   Two bits.   Leap indicator.
                             // vn.   Three bits. Version number of the protocol.
                             // mode. Three bits. Client will pick mode 3 for client.

    uint8_t stratum;         // Eight bits. Stratum level of the local clock.
    uint8_t poll;            // Eight bits. Maximum interval between successive messages.
    uint8_t precision;       // Eight bits. Precision of the local clock.

    uint32_t rootDelay;      // 32 bits. Total round trip delay time.
    uint32_t rootDispersion; // 32 bits. Max error aloud from primary clock source.
    uint32_t refId;          // 32 bits. Reference clock identifier.

    uint32_t refTm_s;        // 32 bits. Reference time-stamp seconds.
    uint32_t refTm_f;        // 32 bits. Reference time-stamp fraction of a second.

    uint32_t origTm_s;       // 32 bits. Originate time-stamp seconds.
    uint32_t origTm_f;       // 32 bits. Originate time-stamp fraction of a second.

    uint32_t rxTm_s;         // 32 bits. Received time-stamp seconds.
    uint32_t rxTm_f;         // 32 bits. Received time-stamp fraction of a second.

    uint32_t txTm_s;         // 32 bits and the most important field the client cares about. Transmit time-stamp seconds.
    uint32_t txTm_f;         // 32 bits. Transmit time-stamp fraction of a second.

  } ntp_packet;              // Total: 384 bits or 48 bytes.

  // Create and zero out the packet. All 48 bytes worth.

  ntp_packet packet = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };

  memset( &packet, 0, sizeof( ntp_packet ) );

  // Set the first byte's bits to 00,011,011 for li = 0, vn = 3, and mode = 3. The rest will be left set to zero.

  *( ( char * ) &packet + 0 ) = 0x1b; // Represents 27 in base 10 or 00011011 in base 2.

  // Create a UDP socket, convert the host-name to an IP address, set the port number,
  // connect to the server, send the packet, and then read in the return packet.

  struct sockaddr_in serv_addr; // Server address data structure.
  struct hostent *server;      // Server data structure.

  sockfd = socket( AF_INET, SOCK_DGRAM, IPPROTO_UDP ); // Create a UDP socket.
  if ( sockfd < 0 ) {
    loge( "ERROR opening socket" );
    return -1;
  }

  for (i = 0; i < ntp_host_cnt; i++)
  {
      host_name = ntp_host_list[i];
      logv("try host: %s", host_name);
      server = gethostbyname( host_name ); // Convert URL to IP.
      if ( server == NULL ) {
        loge( "ERROR, no such host" );
        goto _failed_;
      }

      // Zero out the server address structure.

      bzero( ( char* ) &serv_addr, sizeof( serv_addr ) );

      serv_addr.sin_family = AF_INET;

      // Copy the server's IP address to the server address structure.

      bcopy( ( char* )server->h_addr, ( char* ) &serv_addr.sin_addr.s_addr, server->h_length );

      // Convert the port number integer to network big-endian style and save it to the server address structure.

      serv_addr.sin_port = htons( portno );

      // Call up the server using its IP address and port number.

      if ( connect( sockfd, ( struct sockaddr * ) &serv_addr, sizeof( serv_addr) ) < 0 )
      {
        loge( "ERROR connecting" );
        goto _failed_;
      }

      // Send it the NTP packet it wants. If n == -1, it failed.
      memset( &packet, 0, sizeof( ntp_packet ) );
      // Set the first byte's bits to 00,011,011 for li = 0, vn = 3, and mode = 3. The rest will be left set to zero.
      *( ( char * ) &packet + 0 ) = 0x1b; // Represents 27 in base 10 or 00011011 in base 2.
      n = write( sockfd, ( char* ) &packet, sizeof( ntp_packet ) );
      if ( n < 0 ) {
        loge( "ERROR writing to socket" );
        goto _failed_;
      }

      // Wait and receive the packet back from the server. If n == -1, it failed.
      struct timeval t;
      t.tv_sec = 2;
      t.tv_usec = 0;
      setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, (void *)&t, sizeof(t));
      n = read( sockfd, ( char* ) &packet, sizeof( ntp_packet ) );
      if ( n < 0 ) {
        loge( "ERROR reading from socket" );
        goto _failed_;
      } else {
        // success
        logv( "read success" );
        break;
      }

_failed_:
    continue;
  }
  close(sockfd);
  if (i == ntp_host_cnt) {
      loge("ntp sync failed");
      return -1;
  }

  // These two fields contain the time-stamp seconds as the packet left the NTP server.
  // The number of seconds correspond to the seconds passed since 1900.
  // ntohl() converts the bit/byte order from the network's to host's "endianness".

  packet.txTm_s = ntohl( packet.txTm_s ); // Time-stamp seconds.
  packet.txTm_f = ntohl( packet.txTm_f ); // Time-stamp fraction of a second.

  // Extract the 32 bits that represent the time-stamp seconds (since NTP epoch) from when the packet left the server.
  // Subtract 70 years worth of seconds from the seconds since 1900.
  // This leaves the seconds since the UNIX epoch of 1970.
  // (1900)------------------(1970)**************************************(Time Packet Left the Server)

  time_t txTm = ( time_t ) ( packet.txTm_s - NTP_TIMESTAMP_DELTA );

  // Print the time we got from the server, accounting for local timezone and conversion from UTC time.

  logi( "Time: %s", ctime( ( const time_t* ) &txTm ) );

  struct timeval tv;
  tv.tv_sec = txTm;
  tv.tv_usec = 0;
  settimeofday(&tv, NULL);

  return 0;
}
